﻿using Artech.Core.Configuration;
using Microsoft.Practices.Unity.Utility;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Web;

namespace Artech.Core
{
    /// <summary>
    /// The facade class used to set and get application context.
    /// </summary>
    public class ApplicationContext
    {
        private static ApplicationContext current;
        private object syncHelper = new object();
        private ApplicationContext()
        {
            ApplicationContextSettings settings = ApplicationContextSettings.GetSettings() ?? new ApplicationContextSettings();
            IServiceLocator serviceLocator = ServiceLocatorBuilder.Current.GetServiceLocator(settings.ServiceLocatorName);
            this.Providers = new ServiceLocatableObjectCollection<IContextProvider>(serviceLocator);
            this.Provider = serviceLocator.CanLocateService<IContextProvider>() ? serviceLocator.GetService<IContextProvider>() : new ThreadStaticContextProvider();
        }

        /// <summary>
        /// Gets the context providers.
        /// </summary>
        /// <value>
        /// The context providers.
        /// </value>
        public ServiceLocatableObjectCollection<IContextProvider> Providers { get; private set; }
        
        /// <summary>
        /// Gets the default context provider.
        /// </summary>
        /// <value>
        /// The defualt context provider.
        /// </value>
        public IContextProvider Provider { get; private set; }


        /// <summary>
        /// Gets the current <see cref="ApplicationContext"/>.
        /// </summary>
        /// <value>
        /// The current <see cref="ApplicationContext"/>.
        /// </value>
        public static ApplicationContext Current
        {
            get { return current ?? (current = new ApplicationContext()); }
        }


        /// <summary>
        /// Gets the context item list.
        /// </summary>
        /// <value>
        /// The contect item list.
        /// </value>
        public ContextItemCollection Items
        {
            get { return this.Provider.GetContextItems(); }
        }

        /// <summary>
        /// Gets the value based on the given key.
        /// </summary>
        /// <typeparam name="T">The type of the value.</typeparam>
        /// <param name="key">The key of context item.</param>
        /// <returns></returns>
        public T GetValue<T>(string key)
        {
            Guard.ArgumentNotNullOrEmpty(key, "key");
            ContextItem contextItem;
            if(this.Items.TryGetValue(key, out contextItem))
            {
                if (null != contextItem.Value)
                {
                    return (T)contextItem.Value;
                }
            }
            return default(T);
        }

        /// <summary>
        /// Add a new context item with the given key and value or update the value of the existing context item.
        /// </summary>
        /// <param name="key">The key of context item.</param>
        /// <param name="value">The value of context item. Only value type can be accepted.</param>
        /// <param name="isLocal">if set to <c>true</c> [is local].</param>
        public void SetValue(string key, object value, bool isLocal = true)
        {
            Guard.ArgumentNotNullOrEmpty(key, "key");
            lock (syncHelper)
            {
                ContextItem contextItem;
                if (this.Items.TryGetValue(key, out contextItem))
                {
                    contextItem.Value = value;
                }
                else
                {
                    contextItem = new ContextItem(key, value) { IsLocal = isLocal };
                    this.Items.Add(contextItem);
                }
            }
        }

        /// <summary>
        /// Gets or sets the name of the user.
        /// </summary>
        /// <value>
        /// The name of the user.
        /// </value>
        public string UserName
        {
            get
            {
                string userName = this.GetValue<string>(Keys.UserName);
                if (!string.IsNullOrEmpty(userName))
                {
                    return userName;
                }
                if (null != HttpContext.Current)
                {
                    userName =  HttpContext.Current.User.Identity.Name;
                    this.SetValue(Keys.UserName, userName);
                    return userName;
                }
                userName =  Thread.CurrentPrincipal.Identity.Name;
                this.SetValue(Keys.UserName, userName);
                return userName;
            }
            set { this.SetValue(Keys.UserName, value); }
        }

        /// <summary>
        /// Gets or sets the transaction id.
        /// </summary>
        /// <value>
        /// The transaction id.
        /// </value>
        public string TransactionId
        {
            get { return this.GetValue<string>(Keys.TransactionId); }
            set { this.SetValue(Keys.TransactionId, value); }
        }

        /// <summary>
        /// Gets or sets the name of the activity.
        /// </summary>
        /// <value>
        /// The name of the activity.
        /// </value>
        public string ActivityName
        {
            get { return this.GetValue<string>(Keys.ActivityName); }
            set { this.SetValue(Keys.ActivityName, value); }
        }

        /// <summary>
        /// Gets or sets the current culture.
        /// </summary>
        /// <value>
        /// The current culture.
        /// </value>
        public CultureInfo CurrentCulture
        {
            get
            {
                string culture = this.GetValue<string>(Keys.CurrentCulture);
                if (string.IsNullOrEmpty(culture))
                {
                    culture = Thread.CurrentThread.CurrentCulture.Name;
                    this.SetValue(Keys.CurrentCulture, culture);
                }
                return new CultureInfo(culture);
            }
            set { this.SetValue(Keys.CurrentCulture, value.Name); }
        }

        /// <summary>
        /// Gets or sets the current UI culture.
        /// </summary>
        /// <value>
        /// The current UI culture.
        /// </value>
        public CultureInfo CurrentUICulture
        {
            get
            {
                string culture = this.GetValue<string>(Keys.CurrentUICulture);
                if (string.IsNullOrEmpty(culture))
                {
                    culture = Thread.CurrentThread.CurrentUICulture.Name;
                    this.SetValue(Keys.CurrentUICulture, culture);
                }
                return new CultureInfo(culture);
            }
            set { this.SetValue(Keys.CurrentUICulture, value.Name); }
        }

        public TimeZoneInfo TimeZone
        {
            get
            {
                string timeZoneString = this.GetValue<string>(Keys.TimeZone);
                if (string.IsNullOrEmpty(timeZoneString))
                {
                    timeZoneString = TimeZoneInfo.Local.ToSerializedString();
                    this.SetValue(Keys.TimeZone, timeZoneString);
                    return TimeZoneInfo.Local;
                }
                return TimeZoneInfo.FromSerializedString(timeZoneString);
            }
        }

        /// <summary>
        /// Create a <see cref="DependentContext"/> for asynchronous invocation.
        /// </summary>
        /// <returns>The <see cref="DependentContext"/>.</returns>
        public DependentContext DependentClone()
        {
            return new DependentContext(this.Items);
        }

        /// <summary>
        /// The class defines some keys of common context items.
        /// </summary>
        public class Keys
        {
            public const string UserName = "Artech.Core.ApplicationContext.UserName";
            public const string TransactionId = "Artech.Core.ApplicationContext.TransactionId";
            public const string ActivityName = "Artech.Core.ApplicationContext.UserName";
            public const string CurrentCulture = "Artech.Core.ApplicationContext.CurrentCulture";
            public const string CurrentUICulture = "Artech.Core.ApplicationContext.CurrentUICulture";
            public const string TimeZone = "Artech.Core.ApplicationContext.TimeZone";

        }
    }
}